using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Hosting;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Reflection;
using jMVC.Utils;

// Copyright (c) 2007 Steven Sanderson
// http://blog.codeville.net/
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

[assembly: WebResource("jMVC.Javascript.jMVCplusJson.js", "application/x-javascript")]
namespace jMVC
{
    public class MVCPanel : WebControl, IPostBackDataHandler
    {
        #region Public members / properties
        private string m_ViewUrl;
        /// <summary>
        /// URL of the view (usually a .jmvc file)
        /// Can be absolute, relative, or virtual (e.g. "~/view.jmvc")
        /// Must be on the same domain as the page, otherwise Javascript domain security will block it
        /// </summary>
        public string ViewUrl
        {
            get { return m_ViewUrl; }
            set { m_ViewUrl = value; }
        }

        private PropertyBag m_Model = new PropertyBag();
        /// <summary>
        /// Your data model objects, which must be convertible to JSON
        /// (by being sufficiently "simple" - see docs)
        /// </summary>
        public IPropertyBag Model
        {
            get { return m_Model; }
        }

        private string m_JSControllerObject;
        /// <summary>
        /// Optional: Name of a Javascript object which will be supplied to your 
        /// view under the name "controller". Helpful for encapsulating client-side
        /// controller logic.
        /// </summary>
        public string JSControllerObject
        {
            get { return m_JSControllerObject; }
            set { m_JSControllerObject = value; }
        }

        private bool m_ShowDebugInfo = false;
        /// <summary>
        /// If true, the control will display the JSON data model visibly in the page
        /// </summary>
        public bool ShowDebugInfo
        {
            get { return m_ShowDebugInfo; }
            set { m_ShowDebugInfo = value; }
        }

        private bool m_StoreModelInViewstate = false;
        /// <summary>
        /// You can reduce the ViewState size by keeping this off, but *ONLY* if you don't ever
        /// set the control's "Visible" property to False (otherwise model data will be lost).
        /// </summary>
        public bool StoreModelInViewstate
        {
            get { return m_StoreModelInViewstate; }
            set { m_StoreModelInViewstate = value; }
        }

        #endregion

        #region Page lifecycle events
        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);

            // Ensure a view is provided
            if (string.IsNullOrEmpty(m_ViewUrl))
                throw new ArgumentException("You must specify a view file for the MVCPanel control using the ViewUrl property.");

            // Import the necessary Javascript libraries
            Page.ClientScript.RegisterClientScriptResource(typeof(MVCPanel), "jMVC.Javascript.jMVCplusJson.js");

            // On page load, initialize the jMVC engine
            Page.ClientScript.RegisterStartupScript(GetType(), "init_" + UniqueID, GetInitializationJavascript(GetViewAbsoluteURL()), true);
         }

        private string GetViewAbsoluteURL()
        {
            // Convert the view URL into something the browser will understand
            string viewAbsoluteURL;
            if ((!System.Uri.IsWellFormedUriString(m_ViewUrl, UriKind.Absolute)) && (VirtualPathUtility.IsAppRelative(m_ViewUrl)))
                viewAbsoluteURL = VirtualPathUtility.ToAbsolute(m_ViewUrl);
            else
                viewAbsoluteURL = m_ViewUrl;
            return viewAbsoluteURL;
        }

        private string GetInitializationJavascript(string viewAbsoluteURL)
        {
            StringBuilder javascriptCode = new StringBuilder();

            // First create a JS object to hold the model data
            javascriptCode.AppendFormat("var {0} = eval('('+document.getElementById('{1}').value+')');" + Environment.NewLine, ModelObjectID, GetModelHiddenTextAreaID());

            // Now invoke the jMVC client-side engine, supplying the view and ID of the model textarea
            javascriptCode.AppendFormat("new jMVC({0}, jMVC.prototype.LoadStringFromURL(\"{1}\"), \"{2}\", \"{3}\", {4});" + Environment.NewLine, ModelObjectID, viewAbsoluteURL, ViewElementID, GetModelHiddenTextAreaID(), string.IsNullOrEmpty(JSControllerObject) ? "null" : JSControllerObject);

            return javascriptCode.ToString();
        }

        protected override void Render(HtmlTextWriter writer)
        {
            // Render DIV tag to contain rendered UI
            writer.AddAttribute(HtmlTextWriterAttribute.Id, ViewElementID);
            writer.RenderBeginTag(HtmlTextWriterTag.Div);
            writer.RenderEndTag();

            // Render hidden textarea to contain JSON representation of model
            writer.AddAttribute(HtmlTextWriterAttribute.Id, GetModelHiddenTextAreaID());
            writer.AddAttribute(HtmlTextWriterAttribute.Name, GetModelHiddenTextAreaName());
            if (ShowDebugInfo) {
                // In debug mode: make the textarea visible and quite big
                writer.AddStyleAttribute(HtmlTextWriterStyle.Width, Unit.Percentage(70).ToString());
                writer.AddStyleAttribute(HtmlTextWriterStyle.Height, Unit.Pixel(200).ToString());
            }
            else
                writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none"); // Hidden
            writer.RenderBeginTag(HtmlTextWriterTag.Textarea);
            m_Model.WriteSerializedAsJSON(writer);
            writer.RenderEndTag();

            if (ShowDebugInfo)
                writer.Write("<div style='color:gray'>(MVCPanel is in debug mode: set ShowDebugInfo=\"False\" to hide this)</div>");
        }
        #endregion

        #region Strongly-typed item names
        /// <summary>
        /// Client-side ID of the UI-container element
        /// </summary>
        private string ViewElementID
        {
            get { return this.ClientID + "_view"; }
        }

        /// <summary>
        /// Client-side ID of the TextArea which contains a JSON-representation of the model
        /// </summary>
        private string GetModelHiddenTextAreaID()
        {
            return this.ClientID + "_modelJSON"; // Todo: prevent invalid strings (e.g. ones with spaces)
        }
        /// <summary>
        /// Client-side Name of the TextArea which contains a JSON-representation of the model
        /// </summary>
        private string GetModelHiddenTextAreaName()
        {
            return this.UniqueID; // Todo: prevent invalid strings (e.g. ones with spaces)
        }

        /// <summary>
        /// Client-side name of the Javascript model object
        /// </summary>
        public string ModelObjectID
        {
            get { return this.ClientID + "_model"; }
        }
        #endregion
        
        #region ViewState
        protected override void LoadViewState(object savedState)
        {
            object[] viewstateData = (object[])savedState;
            base.LoadViewState(viewstateData[0]); // Load anything stored by the base class
            m_Model = (PropertyBag)viewstateData[1];
        }

        protected override object SaveViewState()
        {
            object[] viewstateData = new object[2];
            viewstateData[0] = base.SaveViewState(); // Let the base class do any storage it wants

            // Store the model
            m_Model.SerializeFullData = StoreModelInViewstate;
            viewstateData[1] = Model;

            return viewstateData;
        }
 
        #endregion

        #region IPostBackDataHandler Members

        public bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
        {
            if (!string.IsNullOrEmpty(postCollection[postDataKey]))
                m_Model.PopulateFromJSON(postCollection[postDataKey]);

            return false; // Change notifications not supported
        }

        public void RaisePostDataChangedEvent()
        {
            // Not supported
        }

        #endregion
    }
}
